from pydantic import BaseModel
from typing import Dict
import datetime
import pandas as pd
import numpy as np
import shutil
from pathlib import Path
import os
import warnings

warnings.filterwarnings("ignore")


class Performance(BaseModel):
    """
    用于记录策略表现
    """
    annualized_return: float = 0.0  # 年化收益率
    annualized_return_no_fee: float = 0.0  # 无手续费的年化收益率
    annualized_volatility: float = 0.0  # 年化波动率
    annualized_shape: float = 0.0  # 夏普比率
    turnover: float = 0.0  # 换手率, 是日均口径换手率
    margin: float = 0.0  # 代表平均每次收益获利水平，衡量盈利是否覆盖手续费
    fitness: float = 0.0  # 综合衡量夏普比率和Margin的指标
    excess_return: float = 0.0  # 年化超额收益
    relative_return: float = 0.0  # 年化相对收益倍数
    information_ratio: float = 0.0  # 信息比率，衡量超额收益的综合指标
    max_drawdown: float = 0.0  # 最大回撤
    calmar_ratio: float = 0.0  # 综合衡量年化收益和最大回撤的指标
    yearly_count: float = 0.0  # 平均年交易次数
    winning_rate: float = 0.0  # 胜率
    profit_loss_ratio: float = 0.0  # 盈亏比
    kelly_fraction: float = 0.0  # 用凯利公式计算的合理配置仓位


class MaBreakupAndAlignment(object):
    """
    短均线突破长均线且呈现多头排列
    """

    def __init__(self, frequency):
        self.frequency = frequency  # 回测频率
        self.data_begin_date = datetime.date(2015, 1, 1)  # 数据开始日期 数据开始日期小于回测开始日期 预留一些数据计算指标
        self.backtest_begin_date = datetime.date(2017, 1, 1)  # 回测开始日期
        self.backtest_end_date = datetime.date(2025, 8, 31)  # 回测结束日期
        self.ma_l_s_list = [(40, 3), (120, 3), (120, 5)]  # 计算金叉死叉的均线
        self.ma_alignment_values = [i for i in range(3, 102)]  # 多头排列用到的均线
        self.bullish_threshold = 0.7  # 多头排列的阈值
        self.trading_days_one_year = 365  # 一年的交易日数量
        self.risk_free_rate = 2.0  # 无风险利率
        self.btc_df: pd.DataFrame = None  # 保存btc的价格曲线
        self.equity_curve_df_dict: Dict[str, pd.DataFrame] = {}  # 保存资产曲线
        self.trade_log_df_dict: Dict[str, pd.DataFrame] = {}  # 保存交易日志
        self.performance_map = {}  # 保存回测表现
        self.year_list = []  # 回测的年份列表


    def run(self):
        if self.frequency == "1h":
            path = "BTC_kline_1h_merge.csv"
        elif self.frequency == "3h":
            path = "BTC_kline_3h_merge.csv"
        elif self.frequency == "1d":
            path = "BTC_kline_1d_merge.csv"
        else:
            raise Exception("Frequency must be 1h or 3h or 1d!")
        self.btc_df = pd.read_csv(path)
        self.btc_df["datetime"] = pd.to_datetime(self.btc_df["datetime"])
        self.btc_df["date"] = self.btc_df["datetime"].dt.date
        self.btc_df = self.btc_df[self.btc_df['date'] >= self.data_begin_date]
        self.btc_df = self.btc_df[self.btc_df['date'] <= self.backtest_end_date]
        self.cal_bullish_ratio()
        origin_columns = self.btc_df.columns.tolist()
        for (ma_l, ma_s) in self.ma_l_s_list:
            print(f"frequency:{self.frequency}, ma_l:{ma_l}, ma_s:{ma_s}开始回测")
            self.backtest(ma_l, ma_s)
            # 回测完成后删除回测过程中添加的列
            self.btc_df = self.btc_df[origin_columns]
        self.save_performance()

    def backtest(self, ma_l, ma_s):
        self.get_buy_sell_signals(ma_l, ma_s)
        self.ma_breakout_strategy_v1_v2("v1")
        self.ma_breakout_strategy_v1_v2("v2")
        self.ma_breakout_strategy_v3()
        self.get_performance_by_begin_date(ma_l, ma_s)
        self.get_performance_by_years(ma_l, ma_s)
        self.save_file(ma_l, ma_s)

    def get_performance_by_begin_date(self, ma_l, ma_s):
        """
        按照开始日期计算表现
        :return:
        """
        for version in ["v1", "v2", "v3"]:
            version_equity_curve_df = self.equity_curve_df_dict[version]
            version_trade_log_df = self.trade_log_df_dict[version]

            equity_curve_df = version_equity_curve_df.copy()
            hold_btc_df = self.btc_df[self.btc_df['date'] >= self.backtest_begin_date]
            trade_log_df = version_trade_log_df.copy()
            if trade_log_df.empty:
                print(f"frequence:{self.frequency}, 没有发生交易")
            if self.frequency in ("1h", "3h"):
                equity_curve_df = equity_curve_df.groupby("date").last()
                hold_btc_df = hold_btc_df.groupby("date").last()
            nav0_ser = equity_curve_df['equity0'] / equity_curve_df['equity0'].iloc[0]
            nav2_ser = equity_curve_df['equity2'] / equity_curve_df['equity2'].iloc[0]
            hold_btc_nav_ser = hold_btc_df["close"] / hold_btc_df["close"].iloc[0]
            # margin的计算需要用nav0(不含手续费) 其他指标的计算需要用nav2(双边千分之2)
            performance = self.cal_performance(nav2_ser, hold_btc_nav_ser, trade_log_df.copy())
            performance2 = self.cal_performance(nav0_ser, hold_btc_nav_ser, trade_log_df.copy())
            performance.margin = performance2.margin
            performance.annualized_return_no_fee = performance2.annualized_return
            self.performance_map[(version, "all", ma_l, ma_s)] = performance

    def get_performance_by_years(self, ma_l, ma_s):
        """
        逐年分析
        :param nav_df:
        :param trade_log_df:
        :param hold_btc_df:
        :param performance_map:
        :return:
        """
        for version in ["v1", "v2", "v3"]:
            version_equity_curve_df = self.equity_curve_df_dict[version]
            version_trade_log_df = self.trade_log_df_dict[version]
            equity_curve_df = version_equity_curve_df.copy()
            trade_log_df = version_trade_log_df.copy()
            equity_curve_df['year'] = equity_curve_df['date'].apply(lambda x: x.year)
            self.btc_df['year'] = self.btc_df['date'].apply(lambda x: x.year)
            trade_log_df['year'] = trade_log_df['date'].apply(lambda x: x.year)

            for year in equity_curve_df["year"].unique():
                if year not in self.year_list:
                    self.year_list.append(year)
                year_equity_curve_df = equity_curve_df[equity_curve_df["year"] == year]
                year_hold_btc_df = self.btc_df[self.btc_df['year'] == year]
                if self.frequency in ("1h", "3h"):
                    year_equity_curve_df = year_equity_curve_df.groupby("date").last()
                    year_hold_btc_df = year_hold_btc_df.groupby("date").last()
                nav0_ser = year_equity_curve_df['equity0'] / year_equity_curve_df['equity0'].iloc[0]
                nav2_ser = year_equity_curve_df['equity2'] / year_equity_curve_df['equity2'].iloc[0]
                hold_btc_nav_ser = year_hold_btc_df['close'] / year_hold_btc_df['close'].iloc[0]
                year_trade_log_df = trade_log_df[trade_log_df['year'] == year]
                if year_trade_log_df.empty:
                    print(f"frequence:{self.frequency}, year:{year}没有发生交易")
                # 计算margin 需要用nav0的净值曲线(无手续费), 其他指标需要nav2的净值曲线(双边千2)
                performance = self.cal_performance(nav2_ser, hold_btc_nav_ser, year_trade_log_df.copy())
                performance2 = self.cal_performance(nav0_ser, hold_btc_nav_ser, year_trade_log_df.copy())
                performance.margin = performance2.margin
                performance.annualized_return_no_fee = performance2.annualized_return
                self.performance_map[(version, int(year), ma_l, ma_s)] = performance

    def get_buy_sell_signals(self, ma_l, ma_s):
        """
        得到买卖信号
        :return:
        """
        # 突破上轨为买入信号
        self.btc_df["ma_l"] = self.btc_df["close"].rolling(window=ma_l).mean()
        self.btc_df["ma_s"] = self.btc_df["close"].rolling(window=ma_s).mean()
        cond1 = self.btc_df["ma_s"].shift(1) <= self.btc_df["ma_l"].shift(1)  # 之前短均线在长均线下面
        cond2 = self.btc_df["ma_s"] > self.btc_df["ma_l"]  # 现在短均线突破长均线
        self.btc_df.loc[cond1 & cond2, "signal"] = 1
        cond3 = self.btc_df["ma_s"].shift(1) >= self.btc_df["ma_l"].shift(1)  # 之前短均线在长均线上面
        cond4 = self.btc_df["ma_s"] < self.btc_df["ma_l"]  # 现在短均线跌破长均线
        self.btc_df.loc[cond3 & cond4, "signal"] = -1
        self.btc_df["signal"].fillna(0, inplace=True)

    def cal_bullish_ratio(self):
        """计算多头排列比率"""

        def inner_func(row):
            if any(pd.isna(row)):
                return np.nan
            else:
                total_pairs = 0  # 相邻均线对数
                bullish_pairs = 0
                for i in range(1, len(row)):
                    if row[i] < row[i - 1]:  # 短均线>长均线
                        bullish_pairs += 1
                    total_pairs += 1
                return bullish_pairs / total_pairs

        ma_values = []
        for i in self.ma_alignment_values:
            ma_values.append(self.btc_df["close"].rolling(window=i).mean())
        ma_all = pd.concat(ma_values, axis=1)
        self.btc_df["bullish_ratio"] = ma_all.apply(lambda row: inner_func(row), axis=1)

    def ma_breakout_strategy_v1_v2(self, version):
        """v1:短均线突破长均线即开仓, v2: 短均线突破长均线进入等待多头状态,呈多头排列时开仓"""
        # 初始化仓位和净值
        position = 0  # 0表示空仓，1表示多仓
        # 初始资金10000
        equity0 = [10000]  # 没有手续费
        equity1 = [10000]  # 双边手续费千分之一
        fee_rate1 = 0.001
        equity2 = [10000]  # 双边手续费千分之二
        fee_rate2 = 0.002
        trade_log = []
        pre_price = 0
        has_breakup = False  # 是否出现过突破,有的话处在等待多头状态
        truncate_btc_df = self.btc_df[self.btc_df['date'] >= self.backtest_begin_date]
        for _, row in truncate_btc_df.iterrows():
            # 开仓条件: 突破上轨且没有仓位
            if position == 0 and row['signal'] == 1:
                # v1 直接开仓 v2 如果当天呈现多头排列直接开仓 否则进入等待多头状态
                if version == "v1" or row["bullish_ratio"] >= self.bullish_threshold:
                    position = 1
                    entry_price = row["close"]
                    trade_log.append({
                        'datetime': row["datetime"],
                        'action': 'BUY',
                        'price': entry_price,
                    })
                    equity0.append(equity0[-1])
                    equity1.append(equity1[-1] * (1 - fee_rate1))
                    equity2.append(equity2[-1] * (1 - fee_rate2))
                    has_breakup = False  # 取消多头等待状态
                else:
                    has_breakup = True  # 等待多头状态
                    equity0.append(equity0[-1])
                    equity1.append(equity1[-1])
                    equity2.append(equity2[-1])
            # 之前出现过突破现在又呈现多头排列
            elif version == "v2" and has_breakup and row["bullish_ratio"] >= self.bullish_threshold:
                assert position == 0
                position = 1
                entry_price = row["close"]
                trade_log.append({
                    'datetime': row["datetime"],
                    'action': 'BUY',
                    'price': entry_price,
                })
                equity0.append(equity0[-1])
                equity1.append(equity1[-1] * (1 - fee_rate1))
                equity2.append(equity2[-1] * (1 - fee_rate2))
                has_breakup = False  # 取消多头等待状态
            # 之前出现过突破 但并没有等到多头排列就出现了死叉
            elif version == "v2" and has_breakup and row["signal"] == -1:
                has_breakup = False  # 取消多头等待状态
                assert position == 0
                equity0.append(equity0[-1])
                equity1.append(equity1[-1])
                equity2.append(equity2[-1])
            # 平仓条件：回落到中轨且当前有多仓
            elif position == 1 and row['signal'] == -1:
                position = 0
                exit_price = row["close"]
                equity0.append(equity0[-1] * (row["close"] / pre_price))
                equity1.append(equity1[-1] * (row["close"] / pre_price) * (1 - fee_rate1))
                equity2.append(equity2[-1] * (row["close"] / pre_price) * (1 - fee_rate2))
                trade_log.append({
                    'datetime': row["datetime"],
                    'action': 'SELL',
                    'price': exit_price,
                    'return0': (exit_price - entry_price) / entry_price,
                    'return1': (exit_price - entry_price - (entry_price + exit_price) * fee_rate1) / entry_price,
                    'return2': (exit_price - entry_price - (entry_price + exit_price) * fee_rate2) / entry_price,
                })
            # 持有仓位时更新净值
            elif position == 1:
                assert pre_price > 0
                equity0.append(equity0[-1] * (row["close"] / pre_price))
                equity1.append(equity1[-1] * (row["close"] / pre_price))
                equity2.append(equity2[-1] * (row["close"] / pre_price))
            else:
                equity0.append(equity0[-1])
                equity1.append(equity1[-1])
                equity2.append(equity2[-1])
            pre_price = row["close"]
        equity0.pop(0)
        equity1.pop(0)
        equity2.pop(0)
        # 转换为DataFrame
        equity_curve_df = pd.DataFrame({"equity0": equity0,
                                         "equity1": equity1,
                                         "equity2": equity2})
        equity_curve_df["datetime"] = truncate_btc_df["datetime"].reset_index(drop=True)
        equity_curve_df["date"] = truncate_btc_df["date"].reset_index(drop=True)
        trade_log_df = pd.DataFrame(trade_log)
        if not trade_log_df.empty:
            trade_log_df["date"] = trade_log_df["datetime"].dt.date
        else:
            trade_log_df = pd.DataFrame(columns=['datetime', 'action', 'price', 'return', 'date'])
        self.equity_curve_df_dict[version] = equity_curve_df
        self.trade_log_df_dict[version] = trade_log_df


    def ma_breakout_strategy_v3(self):
        """短均线突破长均线开半仓, 呈现多头排列后加仓到全仓"""
        # 初始化仓位和净值
        position = 0  # 0表示空仓，1表示多仓,0.5表示半仓
        # 初始资金10000
        equity0 = [10000]  # 没有手续费
        money0 = 10000  # 资金余额
        equity1 = [10000]
        money1 = 10000
        fee_rate1 = 0.001  # 双边手续费千分之一
        equity2 = [10000]
        money2 = 10000
        fee_rate2 = 0.002  # 双边手续费千分之二
        trade_log = []
        pre_price = 0
        truncate_btc_df = self.btc_df[self.btc_df['date'] >= self.backtest_begin_date]
        for _, row in truncate_btc_df.iterrows():
            # 开仓条件: 突破上轨且没有仓位(只开50%的仓位)
            if position == 0 and row['signal'] == 1:
                open_price = row["close"]  # 开仓价格
                # 断言:空仓时 资金余额=总资产
                assert money0 == equity0[-1]
                assert money1 == equity1[-1]
                assert money2 == equity2[-1]
                # 如果已经呈现多头排列 直接开全仓
                if row["bullish_ratio"] >= self.bullish_threshold:
                    # 分别计算不同手续费的情况下的开仓成交量, 总的成交金额(含手续费)=资金余额
                    open_vol0 = (equity0[-1]) / open_price
                    open_vol1 = (equity1[-1]) / open_price * (1 - fee_rate1)
                    open_vol2 = (equity2[-1]) / open_price * (1 - fee_rate2)
                    add_vol0 = 0  # 直接全仓不需要加仓, 所以加仓量=0
                    add_vol1 = 0
                    add_vol2 = 0
                    trade_log.append({
                        'datetime': row["datetime"],
                        'action': 'BUY_ALL',
                        'price': open_price,
                    })
                    # 分别计算不同手续费其概况下的 开仓成本 和 加仓成本
                    open_cost0 = money0
                    open_cost1 = money1
                    open_cost2 = money2
                    add_cost0 = 0  # 直接全仓不需要加仓, 所以加仓成本=0
                    add_cost1 = 0
                    add_cost2 = 0
                    # 分别计算不同手续费的情况下的 资金余额
                    money0 = 0  # 开全仓 资金余额 = 0
                    money1 = 0
                    money2 = 0
                    position = 1  # 开全仓
                else:
                    # 分别计算不同手续费的情况下的开仓成交量, 总的成交金额(含手续费)=资金余额*50%
                    open_vol0 = (equity0[-1] * 0.5) / open_price
                    open_vol1 = (equity1[-1] * 0.5) / open_price * (1 - fee_rate1)
                    open_vol2 = (equity2[-1] * 0.5) / open_price * (1 - fee_rate2)
                    trade_log.append({
                        'datetime': row["datetime"],
                        'action': 'BUY_50%',
                        'price': open_price,
                    })
                    # 分别计算不同手续费情况下的 开仓成本
                    open_cost0 = money0 * 0.5
                    open_cost1 = money1 * 0.5
                    open_cost2 = money2 * 0.5
                    # 分别计算不同手续费的情况下的 资金余额
                    money0 *= 0.5
                    money1 *= 0.5
                    money2 *= 0.5
                    position = 0.5  # 开半仓
                # 今天的总资产, 只需用昨天的总资产减去开仓手续费即可
                equity0.append(equity0[-1])
                equity1.append(equity1[-1] - open_vol1 * open_price * fee_rate1)
                equity2.append(equity2[-1] - open_vol2 * open_price * fee_rate2)
            # 如果在半仓的情况下出现多头排列 则加仓到满仓
            elif position == 0.5 and row["bullish_ratio"] >= self.bullish_threshold:
                # 计算加仓前的总资产:持仓部分的市值变化导致的总资产变化
                tmp_equity0 = (equity0[-1] - money0) * (row["close"] / pre_price) + money0
                tmp_equity1 = (equity1[-1] - money1) * (row["close"] / pre_price) + money1
                tmp_equity2 = (equity2[-1] - money2) * (row["close"] / pre_price) + money2
                add_price = row["close"]  # 加仓价格
                # 分别计算不同手续费的情况下的加仓成交量, 成交金额(含手续费)=资金余额,全部用完
                add_vol0 = money0 / add_price
                add_vol1 = money1 / add_price * (1 - fee_rate1)
                add_vol2 = money2 / add_price * (1 - fee_rate2)
                trade_log.append({
                    'datetime': row["datetime"],
                    'action': 'ADD_50%',
                    'price': add_price,
                })
                # 分别计算不同手续费其概况下的 加仓成本
                add_cost0 = money0
                add_cost1 = money1
                add_cost2 = money2
                money0 = 0  # 加仓后剩余资金变成0
                money1 = 0
                money2 = 0
                # 加仓后的总资产, 用前面计算的加仓前总资产减去加仓手续费即可
                equity0.append(tmp_equity0)
                equity1.append(tmp_equity1 - add_vol1 * add_price * fee_rate1)
                equity2.append(tmp_equity2 - add_vol2 * add_price * fee_rate2)
                position = 1  # 加仓后位全仓
            # 平仓条件：回落到中轨且当前有多仓
            elif position > 0 and row['signal'] == -1:
                exit_price = row["close"]
                # 平仓有两种情况 全仓平仓 和 半仓平仓
                if position == 1:
                    # 分别计算不同手续费的情况下的卖出收入
                    sell_revenue0 = (open_vol0 + add_vol0) * exit_price
                    sell_revenue1 = (open_vol1 + add_vol1) * exit_price * (1 - fee_rate1)
                    sell_revenue2 = (open_vol2 + add_vol2) * exit_price * (1 - fee_rate2)
                    # 全仓时断言 资金余额= 0
                    assert money0 == 0
                    assert money1 == 0
                    assert money2 == 0
                    money0 += sell_revenue0  # 资金余额增加
                    money1 += sell_revenue1
                    money2 += sell_revenue2
                    return0 = sell_revenue0 / (open_cost0 + add_cost0) - 1  # return=卖出收入/（开仓成本+加仓成本)-1
                    return1 = sell_revenue1 / (open_cost1 + add_cost1) - 1
                    return2 = sell_revenue2 / (open_cost2 + add_cost2) - 1
                else:
                    # 半仓平仓
                    assert position == 0.5
                    sell_revenue0 = open_vol0 * exit_price  # 卖出收入
                    sell_revenue1 = open_vol1 * exit_price * (1 - fee_rate1)
                    sell_revenue2 = open_vol2 * exit_price * (1 - fee_rate2)
                    money0 += sell_revenue0  # 资金余额增加
                    money1 += sell_revenue1
                    money2 += sell_revenue2
                    return0 = sell_revenue0 / open_cost0 - 1  # 没有加仓,只计算半仓部分的return
                    return1 = sell_revenue1 / open_cost1 - 1
                    return2 = sell_revenue2 / open_cost2 - 1
                equity0.append(money0)  # 平仓后 资金余额=总资产
                equity1.append(money1)
                equity2.append(money2)
                trade_log.append({
                    'datetime': row["datetime"],
                    'action': 'SELL_ALL' if position == 1 else 'SELL_50%',
                    'price': exit_price,
                    'return0': return0,
                    'return1': return1,
                    'return2': return2,
                })
                position = 0  # 卖出后空仓
            # 持有仓位时更新净值
            elif position == 1:
                assert pre_price > 0
                equity0.append(equity0[-1] * (row["close"] / pre_price))
                equity1.append(equity1[-1] * (row["close"] / pre_price))
                equity2.append(equity2[-1] * (row["close"] / pre_price))
            elif position == 0.5:
                assert pre_price > 0
                equity0.append((equity0[-1] - money0) * (row["close"] / pre_price) + money0)  # 半仓时只更新持仓部分的市值变动
                equity1.append((equity1[-1] - money1) * (row["close"] / pre_price) + money1)
                equity2.append((equity2[-1] - money2) * (row["close"] / pre_price) + money2)
            else:
                equity0.append(equity0[-1])
                equity1.append(equity1[-1])
                equity2.append(equity2[-1])
            pre_price = row["close"]
        equity0.pop(0)
        equity1.pop(0)
        equity2.pop(0)
        # 转换为DataFrame
        equity_curve_df = pd.DataFrame({"equity0": equity0,
                                         "equity1": equity1,
                                         "equity2": equity2})
        equity_curve_df["datetime"] = truncate_btc_df["datetime"].reset_index(drop=True)
        equity_curve_df["date"] = truncate_btc_df["date"].reset_index(drop=True)
        trade_log_df = pd.DataFrame(trade_log)
        if not trade_log_df.empty:
            trade_log_df["date"] = trade_log_df["datetime"].dt.date
        else:
            trade_log_df = pd.DataFrame(columns=['datetime', 'action', 'price', 'return', 'date'])
        self.equity_curve_df_dict["v3"] = equity_curve_df
        self.trade_log_df_dict["v3"] = trade_log_df

    @staticmethod
    def get_trade_count(trade_log_df):
        """开半仓只算交易0.5次,开全仓才算交易1次"""
        count_ser = trade_log_df["action"].apply(lambda x: 0.5 if "50" in x else 1)
        return count_ser.sum()

    def cal_performance(self, nav_series, nav_series_benchmark, trade_log_df):
        """
        计算策略表现
        :param nav_series:  策略每日净值曲线
        :param nav_series_benchmark:  基准每日净值曲线
        :param trade_log_df:  期间的交易记录
        :return:
        """
        performance_obj = Performance()  # 创建一个performance对象
        if nav_series.max() == nav_series.min() == 1 and trade_log_df.empty:  # 期间都是空仓, 没有买卖行为
            no_performance = Performance(annualized_return=np.nan,
                                         annualized_return_no_fee=np.nan,
                                         annualized_volatility=np.nan,
                                         annualized_shape=np.nan,
                                         turnover=np.nan,
                                         margin=np.nan,
                                         fitness=np.nan,
                                         excess_return=np.nan,
                                         relative_return=np.nan,
                                         information_ratio=np.nan,
                                         max_drawdown=np.nan,
                                         calmar_ratio=np.nan,
                                         yearly_count=np.nan,
                                         winning_rate=np.nan,
                                         profit_loss_ratio=np.nan,
                                         kelly_fraction=np.nan)
            return no_performance
        # 日收益率
        daily_returns = nav_series.pct_change().dropna()
        benchmark_returns = nav_series_benchmark.pct_change().dropna()
        # 总收益率
        total_return = nav_series.iloc[-1] / nav_series.iloc[0] - 1
        # 年化收益率
        n_days = len(nav_series)  # 回测期间的交易天数
        annualized_return = (1 + total_return) ** (self.trading_days_one_year / n_days) - 1
        annualized_return = annualized_return * 100
        performance_obj.annualized_return = annualized_return
        # 年化波动率
        annualized_volatility = daily_returns.std() * np.sqrt(self.trading_days_one_year)
        annualized_volatility = annualized_volatility * 100
        performance_obj.annualized_volatility = annualized_volatility
        # 夏普比率 risk_free_rate=2.0
        annualized_shape = (annualized_return - self.risk_free_rate) / annualized_volatility if (
                annualized_volatility != 0) else np.nan
        performance_obj.annualized_shape = annualized_shape
        # 平均年交易次数
        if "BUY_50%" in set(trade_log_df["action"]):
            yearly_count = self.get_trade_count(trade_log_df) / (n_days / 365)
        else:
            yearly_count = len(trade_log_df) / (n_days / 365)
        performance_obj.yearly_count = yearly_count
        turnover = yearly_count / 365 * 100
        performance_obj.turnover = turnover

        # margin 以千分比的形式呈现  Margin=DailyReturn/TurnOver
        # 用年化return求daily_return
        daily_return = (annualized_return / 100 + 1) ** (1 / 365) - 1
        margin = daily_return * 100 / turnover if turnover != 0 else np.nan  # 乘以100是为了和turnover保持单位统一
        # fitness, Fitness=Annualized Sharpe×Sqrt(Abs(Margin))
        fitness = annualized_shape * np.sqrt(abs(margin))
        # 先计算fitness再以千分比的形式呈现
        margin = margin * 1000
        performance_obj.margin = margin
        performance_obj.fitness = fitness

        # max_drawdown
        peak = nav_series.expanding().max()
        drawdown = (nav_series - peak) / peak
        max_drawdown = abs(drawdown.min())
        max_drawdown = max_drawdown * 100
        performance_obj.max_drawdown = max_drawdown

        # 超额收益指标
        benchmark_annualized_return = (nav_series_benchmark.iloc[-1] / nav_series_benchmark.iloc[0]) ** (
                self.trading_days_one_year / n_days) - 1
        benchmark_annualized_return = benchmark_annualized_return * 100  # benchmark的年化收益率
        performance_obj.excess_return = annualized_return - benchmark_annualized_return

        # 年化相对收益倍数
        performance_obj.relative_return = (annualized_return / 100 + 1) / (benchmark_annualized_return / 100 + 1)

        # information_ration
        # benchmark_return 是持有BTC的每日return序列 trading_days=365
        excess_returns = daily_returns.reset_index(drop=True) - benchmark_returns.reset_index(drop=True)
        excess_returns_percent = excess_returns * 100
        information_ratio = performance_obj.excess_return / (
                excess_returns_percent.std() * np.sqrt(self.trading_days_one_year))
        performance_obj.information_ratio = information_ratio

        # calmar ratio, Calmar Ratio = Annualized Return/Max Drawdown
        calmar_ratio = annualized_return / abs(max_drawdown) if max_drawdown != 0 else np.nan  # 上下都乘了100 不用处理
        performance_obj.calmar_ratio = calmar_ratio

        # 计算胜率 盈亏比 kelly_fraction
        sell_df = trade_log_df[trade_log_df['action'].str.contains('SELL')]
        if len(sell_df) == 0:
            performance_obj.winning_rate = np.nan  # 胜率置位空
            performance_obj.profit_loss_ratio = np.nan  # 盈亏比置位空
            performance_obj.kelly_fraction = np.nan  # kelly_fraction置位空
        else:
            # 胜率
            winning_rate = len(sell_df[sell_df["return2"] > 0]) / len(sell_df)
            performance_obj.winning_rate = winning_rate * 100
            # 盈亏比
            win_df = sell_df[sell_df["return2"] > 0]
            lose_df = sell_df[sell_df["return2"] < 0]
            performance_obj.profit_loss_ratio = abs(win_df["return2"].mean() / lose_df["return2"].mean())
            # 用凯利公式计算的合理配置仓位
            performance_obj.kelly_fraction = (performance_obj.profit_loss_ratio * winning_rate - (1 - winning_rate)) / (
                performance_obj.profit_loss_ratio)

        return performance_obj

    def save_file(self, ma_l, ma_s):
        """
        保存所有文件
        :param ma_l:
        :param ma_s:
        :return:
        """
        for version in ["v1", "v2", "v3"]:
            version_equity_curve_df = self.equity_curve_df_dict[version]
            version_trade_log_df = self.trade_log_df_dict[version]

            equity_curve_df = version_equity_curve_df.copy()
            hold_btc_df = self.btc_df[self.btc_df['date'] >= self.backtest_begin_date]
            trade_log_df = version_trade_log_df.copy()
            trade_log_df.to_csv(f"{self.frequency}/trade_log/trade_log_{ma_l}_{ma_s}_{version}.csv", index=False)
            if self.frequency in ("1h", "3h"):
                equity_curve_df = equity_curve_df.groupby("date").last()
                hold_btc_df = hold_btc_df.groupby("date").last()
                hold_btc_df.reset_index(drop=False, inplace=True)
            nav0_ser = equity_curve_df['equity0'] / equity_curve_df['equity0'].iloc[0]
            nav1_ser = equity_curve_df['equity1'] / equity_curve_df['equity1'].iloc[0]
            nav2_ser = equity_curve_df['equity2'] / equity_curve_df['equity2'].iloc[0]
            hold_btc_nav_ser = hold_btc_df["close"] / hold_btc_df["close"].iloc[0]
            # 等比例放缩放 中轨 上轨 下轨的值, 主要用于画图
            ma_l_ser = hold_btc_df["ma_l"] / hold_btc_df["close"].iloc[0]
            ma_s_ser = hold_btc_df["ma_s"] / hold_btc_df["close"].iloc[0]
            nav_df = pd.DataFrame({"date": hold_btc_df["date"].values,
                                   "nav0": nav0_ser.values,
                                   "nav1": nav1_ser.values,
                                   "nav2": nav2_ser.values,
                                   "nav(hold_btc)": hold_btc_nav_ser.values,
                                   "ma_l": ma_l_ser.values,
                                   "ma_s": ma_s_ser.values, })
            nav_df.to_csv(f"{self.frequency}/nav/nav_{ma_l}_{ma_s}_{version}.csv", index=False)

    def save_performance(self):
        # 调整一下指标的顺序
        columns = ["version", "ma_l", "ma_s", "ma_alignment_values", "bullish_threshold", "annualized_shape", "annualized_return",
                   "annualized_return_no_fee",
                   "excess_return",
                   "calmar_ratio",
                   "yearly_count", "margin", "winning_rate", "profit_loss_ratio",
                   "kelly_fraction", "annualized_volatility",
                   "turnover", "fitness", "relative_return", "max_drawdown", "information_ratio"]
        results = []
        for (version, time, ma_l, ma_s), value in self.performance_map.items():
            if time == "all":
                performance_dict = value.dict()
                performance_dict.update({'version': version, 'ma_l': ma_l, 'ma_s': ma_s})
                results.append(performance_dict)
        results_df = pd.DataFrame(results)
        results_df["ma_alignment_values"] = f"{self.ma_alignment_values[0]}-{self.ma_alignment_values[-1]}"
        results_df["bullish_threshold"] = self.bullish_threshold
        results_df = results_df[columns]
        results_df.sort_values(by=['version', 'ma_l', 'ma_s'], inplace=True)
        results_df.to_csv(f"{self.frequency}/result/backtest_performance.csv", index=False)

        all_df = pd.DataFrame()
        for year in self.year_list:
            results = []
            for (version, time, ma_l, ma_s), value in self.performance_map.items():
                if time == year:
                    performance_dict = value.dict()
                    performance_dict.update({'version': version, 'ma_l': ma_l, 'ma_s': ma_s})
                    results.append(performance_dict)
            results_df = pd.DataFrame(results)
            results_df.sort_values(by=['version', 'ma_l', 'ma_s'], inplace=True)
            results_df['year'] = year
            results_df["ma_alignment_values"] = f"{self.ma_alignment_values[0]}-{self.ma_alignment_values[-1]}"
            results_df["bullish_threshold"] = self.bullish_threshold
            results_df = results_df[['year'] + columns]
            all_df = pd.concat([all_df, results_df])
        all_df.reset_index(drop=True, inplace=True)
        all_df.to_csv(f"{self.frequency}/backtest_performance_by_year.csv", index=False)


def delete_all_folders(current_dir="."):
    """
    删除当前目录下的所有文件夹（保留文件）
    """
    current_path = Path(current_dir)

    if not current_path.exists():
        print("当前路径不存在！")
        return

    folders_deleted = 0
    errors = 0

    # 遍历当前目录下的所有项目
    for item in current_path.iterdir():
        if item.is_dir():
            try:
                # 使用 shutil.rmtree 删除文件夹及其内容
                shutil.rmtree(item)
                print(f"✅ 已删除文件夹: {item.name}")
                folders_deleted += 1
            except Exception as e:
                print(f"❌ 删除文件夹失败 {item.name}: {e}")
                errors += 1

    print(f"\n操作完成！删除了 {folders_deleted} 个文件夹，{errors} 个错误")


def main():
    print(datetime.datetime.now())
    delete_all_folders()
    for frequency in ["1d"]:
        os.makedirs(f"{frequency}/trade_log")
        os.makedirs(f"{frequency}/nav")
        os.makedirs(f"{frequency}/png")
        os.makedirs(f"{frequency}/result")
        backtest_obj = MaBreakupAndAlignment(frequency)
        backtest_obj.run()
    print(datetime.datetime.now())


if __name__ == "__main__":
    main()
